fix(app-shell): render the envelope's producer-marked userMessage in the Studio package lookup - #7960
Merged
Merged
Conversation
…the Studio package lookup `fetchFullPackage` reads the ADR-0112 failure envelope for the words it shows the author, and read only `error.message` and `error.code`. The platform's package door also emits `error.userMessage` — the text a producer marked AT THROW TIME as addressed to the end user, whose presence IS the marking and which a consumer that sees it is meant to render verbatim. The two compose badly in the 5xx band. That door withholds the producer's PROSE and substitutes the generic `Internal server error` into `message`, but the withhold rewrites a local `message` const and the leak predicate is only ever handed `thrown.message`, so `userMessage` is never an input to it and rides through a sanitised 500 untouched. An author meeting a marked 500/503 was shown the generic sentence and the specific one silently had nowhere to appear. Prefer the mark over the diagnostic, at any status — the producing door applies no status condition to this channel, so honouring it in one band only would re-create on the reading end the divergence that door refused to create on the writing end. `code` still travels with whichever prose won, and an unmarked refusal (no `userMessage`, or a non-string one) falls through to `message` with byte-identical output. The read is shared, so both of its callers benefit: the management sheet behind "Package info & settings" and the managed-snapshot refresh. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3
…, measured The ablation run (fix reverted) turned 7 of the 15 pins red, not the 8 the file's header implied. The one that stayed green is §2's `reports once, on the shared sonner id`: it asserts the report CHANNEL — objectui#7368's posture of one outage, one toast, one id — and reverting the read changes only the WORDS. Annotated rather than strengthened. It is doing a real job that none of the words-pins do: a fix that reached for a second reporting channel to carry the marked text would satisfy every words-pin and fail this one. Recording the measured colour so the header does not overclaim which pins discriminate. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3
Contributor
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
os-sam
marked this pull request as ready for review
September 6, 2026 05:01
This was referenced Sep 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #7938
fetchFullPackagereads the ADR-0112 failure envelope for the words it shows the author,and read only
error.messageanderror.code. The platform's package door also emitserror.userMessage— the text a producer marked at throw time as addressed to the enduser. This teaches the reader to prefer it.
Verified on the shipping tree at
15c57f85a.The field really does arrive — measured in the producer, not assumed
The card's evidence chain has a leg in another repo, so it was checked before a line was
written.
GET /api/v1/packagesis served by the direct-mount registrar,packages/rest/src/package-routes.ts. ItssendThrownErrorwithholds the prose in the5xx band:
and then spreads the marked channel onto the wire beside that local, untouched by it:
That door's own note states the rule, and it is the answer to the question the card asked:
sendError(packages/types/src/response-envelope.ts) nestsextraundererror:which is exactly the
error.userMessagepath this PR reads. The framework pins the pairin
packages/rest/src/package-door-user-message.test.ts§4, assertingerror.message === INTERNAL_ERROR_MESSAGEand the marked text intact on the same body.So the premise holds: in the 5xx band
messageis replaced by the generic sentence anduserMessageis not withheld with it. No cross-repo change is produced by this PR; theframework was read only.
Why
userMessageoutranksmessage, and why not only in the 5xx bandThe priority is the contract's. The envelope writer's own words:
The card offered a 5xx-only variant. It is declined deliberately, and the reason is in the
producing door: it applies no status condition to this channel, on purpose. A consumer
that honoured the mark in one band only would re-create on the reading end precisely the
divergence that door refused to create on the writing end. A 4xx
messageis alreadycaller-facing by design — but when a producer also marked a text there, the mark is the
more specific answer to "what should this person read", and the diagnostic it displaces is
not lost to diagnosis:
codestill travels.This is also not a tolerant alias ladder. These are two declared fields with different
meanings, not two spellings of one fact.
Three combinations, all pinned
Both fields are optional and independent. The new pin file drives all three, not only the
one that motivated the card — reading only the "both present" case is exactly what let
this survive.
messageonly (the common unmarked refusal)the sentence (CODE)userMessageonlyHTTP 503the marked sentence (CODE)Internal server error (CODE)the marked sentence (CODE)HTTP 503The first row is load-bearing: it is the pin that stops "prefer
userMessage" from beingimplemented as "read
userMessageinstead", which would have blanked every refusalthe platform serves today.
codecomposes orthogonally, and that is pinned too: it is appended to whichever prosewon; it never rescues a body carrying no prose (
codealone still yields the bare status);and a non-string or empty-string
userMessageis not a mark, so it falls through tomessage. The producer's owndeclaredUserMessagealready applies that non-empty rule —the reader does not depend on it doing so.
The four callers of this endpoint, named individually
This is a shared read, so the beneficiaries were measured rather than inherited from the
card — and the measurement corrects the card on one point. The card described "four
beneficiaries" of this fix. The four named are the four callers of
GET /api/v1/packageson this surface (which is what the
openManagedocblock says), and they split two ways:onManageChangedhead)packages-iofetchPackagespackages-iofetchPackagespackages-iofetchPackagesfetchFullPackageThe first three go through
fetchPackagesinpackages-io.ts, which isif (!res.ok) throw new Error(...)on the status alone — it never opens the body, so ithas no
messageto lose and nouserMessageto gain. They cannot benefit from anenvelope-reading fix because they do not read the envelope. That is a real and strictly
larger defect, in a different file and outside this card's declared surface, so it is
filed separately as #7959 rather than ridden in here.
fetchFullPackageitself has exactly two callers, and both change:openManage) — a markedrefusal now reaches the author through this surface's existing objectui#7368 toast on
the shared
studio-package-listsonner id.onManageChanged's tail, objectui#7907) — the markedsentence travels inside the existing
engine.studio.pkg.manageRefreshFailedmessage.Same toast, same shared sonner id, same formatter: one mechanism, not a second. §5
of the new pin file drives this caller specifically so it is shown, not assumed.
The tail's own behaviour (report, then dismiss the panel) belongs to objectui#7907 and is untouched
here; this PR changes only the words it carries.
Evidence
Reverse validation, direction predicted before running: with the fix reverted (mutation
proven on disk by occurrence count and a blob hash differing from HEAD; restored via
git checkout HEAD -- ...under a trap and proven back by an emptygit diff HEADplus ablob hash identical to the HEAD blob
79a9cf7f0edc1c40b2c8ffa862b3393484bbed37):The prediction was 8 red; 7 measured. The difference is recorded rather than papered over:
§2's "reports once, on the shared sonner id" asserts the report channel, not the words,
and reverting the read changes only the words. It is annotated in the file as green either
way, and it still does a job no words-pin does — a fix that reached for a second reporting
channel would pass every words-pin and fail that one.
Green on the shipping tree:
Test Files 1 passed,Tests 15 passedstudio-designdirectory (52 files)Test Files 52 passed,Tests 299 passedTest Files 6 passed,Tests 41 passed@object-ui/app-shelltype-checkerror TS@object-ui/app-shelllintcheck:control-bytes·check:changeset-presence·check:changeset-no-major·check:vi-mock-specifiers·check:vi-mock-inherit·check:i18n-keys·check:unreferenced-sources·check:entry-guardEvery vitest run reports
RUN v4.1.10 /home/user/objectui-issue-7938— the repositoryroot, not a package sub-root (objectui#3378's silent-false-green). Type-check non-vacuity
was proven with
tsc -p tsconfig.test.json --listFiles: both edited files are in the4416-file program. Lint narrowing needed no argument — the whole package was scanned, and
both edited files are present in eslint's own 1082-file population with 0 errors and no
new warnings (the changed file's 19 warnings are all pre-existing, none within the added
line range 401–449).
check:eager-closure-budgetis NOT MEASURED, not green: it exits 2 declaring its ownprerequisite unmet ("No eager-closure report at
apps/console/dist/eager-closure.json, sono chunk was weighed... This is a broken gauge, not 4 budgets that all passed"). It needs a
console build; CI owns that run.
Clause-② =
no多读一个已经在线上的响应字段并渲染它:不改任何 schema 的接受/拒绝行为,不扩大公开面,不移动任何门的扫描 population。
Generated by Claude Code